use salvo::{
    fs::NamedFile,
    oapi::{endpoint, extract::PathParam},
    Depot, Request, Response,
};
use validator::Validate;

use crate::{
    app_error::AppError,
    app_writer::{AppResult, AppWriter},
    dtos::files::{
        FileAddRequest, FileAddRequestQuick, FileDeleteRequest, FileDownloadRequest, FileResponse,
    },
    entities::files::File,
    search_engine::{get_search_engine_file, SearchEngine},
    services::{
        file::{self, add_file, add_file_quick, files},
        paper::is_own_paper,
    },
};
use salvo::Writer;

use super::get_userid_from_jwt;

#[endpoint(tags("files"))]
pub async fn get_files(
    id: PathParam<String>,
    depot: &mut Depot,
) -> AppResult<AppWriter<Vec<FileResponse>>> {
    let user_id = get_userid_from_jwt(depot)?;
    if is_own_paper(user_id, &id).await? {
        let res = files(&id.0).await;
        let res = AppWriter(res);
        Ok(res)
    } else {
        let err = AppError::AuthError("no such file");
        Err(err)
    }
}

#[endpoint(tags("files"),
parameters(
    ("id", description = "paper id"),
    ("hash", description = "file hash")
))]
pub async fn get_download_file(
    req: &mut Request,
    res: &mut Response,
    depot: &mut Depot,
) -> AppResult<()> {
    let user_id = get_userid_from_jwt(depot)?;
    let req_: FileDownloadRequest = req.extract().await?;
    if is_own_paper(user_id, &req_.id).await? {
        let (file_name, file_path) = file::download_file(req_).await?;
        let builder = NamedFile::builder(file_path).attached_name(file_name);
        builder.send(req.headers(), res).await;
        Ok(())
    } else {
        let err = AppError::AuthError("no such file");
        Err(err)
    }
}

#[endpoint(tags("files"),
parameters(
    ("id", description = "paper id"),
))]
pub async fn post_upload_file(req: &mut Request) -> AppResult<AppWriter<FileResponse>> {
    let req_: FileAddRequest = req.extract().await?;
    req_.validate()?;
    let file = req
        .file("data")
        .await
        .ok_or(AppError::BadRequest("no file uploaded"))?;
    let data = tokio::fs::read(file.path()).await?;
    if data.is_empty() {
        return Err(AppError::BadRequest("empty file uploaded"));
    };
    let res = add_file(req_, data).await;
    if let Ok(res) = &res {
        let search_engine = get_search_engine_file().await?;
        let file = File::from(res.clone());
        let _ = tokio::spawn(async {
            if let Err(e) = async {
                search_engine.add_file(file).await?;
                search_engine.commit().await?;
                Ok::<(), AppError>(())
            }
            .await
            {
                tracing::error!("search engine add file error: {}", e);
            }
        });
    }
    let res = AppWriter(res);
    Ok(res)
}

#[endpoint(tags("files"),
parameters(
    ("id", description = "paper id"),
    ("hash", description = "file hash")
))]
pub async fn post_upload_file_quick(req: &mut Request) -> AppResult<AppWriter<FileResponse>> {
    let req: FileAddRequestQuick = req.extract().await?;
    let res = add_file_quick(req).await;
    let res = AppWriter(res);
    Ok(res)
}

#[endpoint(tags("files"),
parameters(
    ("id", description = "paper id"),
    ("hash", description = "file hash")
))]
pub async fn delete_file(req: &mut Request, depot: &mut Depot) -> AppResult<AppWriter<()>> {
    let user_id = get_userid_from_jwt(depot)?;
    let req: FileDeleteRequest = req.extract().await?;
    if is_own_paper(user_id, &req.id).await? {
        let res = file::delete_file(req).await;
        let res = AppWriter(res);
        Ok(res)
    } else {
        let err = AppError::AuthError("no such file");
        Err(err)
    }
}
